<template>
  <div ref="viewerContainer" class="bpmn-viewer">
    <div class="process-top-container">
      <div class="my-process-status">
        <span class="intro">状态：</span>
        <span class="finish">已完成</span>
        <span class="processing">流程中</span>
        <span class="todo">未进行</span>
      </div>
      <!-- 添加缩放控制按钮 -->
      <div class="zoom-controls">
        <button class="zoom-btn" :disabled="!canZoom" @click="handleZoomIn">
          <i class="fa fa-search-plus" /> 放大
        </button>
        <button class="zoom-btn" :disabled="!canZoom" @click="handleZoomOut">
          <i class="fa fa-search-minus" /> 缩小
        </button>
        <button class="zoom-btn" :disabled="!canZoom" @click="handleFitToScreen">
          <i class="fa fa-expand" /> 适应屏幕
        </button>
        <span class="zoom-level">当前缩放: {{ currentZoom.toFixed(2) }}x</span>
      </div>
    </div>
    <div ref="canvas" class="bpmn-canvas" />
  </div>
</template>
<script setup>
import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
import BpmnViewer from 'bpmn-js/lib/Viewer'
import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda.json'
import 'bpmn-js/dist/assets/diagram-js.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'

const props = defineProps({
  completedNodes: {
    type: Array,
    default: () => []
  },
  rejectNodes: {
    type: Array,
    default: () => []
  },
  processData: {
    type: String,
    default: ''
  },
  containerWidth: {
    type: [String, Number],
    default: '100%'
  },
  containerHeight: {
    type: [String, Number],
    default: '500px'
  },
  autoFit: {
    type: Boolean,
    default: true
  },
  autoCenter: {
    type: Boolean,
    default: true
  },
  fitPadding: {
    type: Number,
    default: 50
  }
})

const viewerContainer = ref(null)
const canvas = ref(null)
const modeler = ref(null)
const processXml = ref('')
const highlightXml = ref('')
const currentZoom = ref(1.0)
const zoomMin = 0.5
const zoomMax = 2.0
const zoomStep = 0.1
const isChartReady = ref(false)
const retryCount = ref(0)
const maxRetries = 5
const lastKnownWidth = ref(0)
const lastKnownHeight = ref(0)
const isResizing = ref(false)
const isImporting = ref(false)
const eventListeners = ref([])
const zoomingDisabled = ref(false)
const zoomInClicks = ref(0)
const zoomOutClicks = ref(0)
const fitToScreenClicks = ref(0)
const centerClicks = ref(0)
const viewState = ref({
  isCentered: false,
  isFitted: false
})

const canZoom = computed(() => {
  return !zoomingDisabled.value && modeler.value && isChartReady.value
})

watch(() => props.completedNodes, () => {
  if (props.completedNodes) {
    applyHighlight()
  }
}, { deep: true, immediate: true })

watch(() => props.containerWidth, () => {
  updateContainerSize()
})
watch(() => props.containerHeight, () => {
  updateContainerSize()
})

async function updateContainerSize() {
  if (viewerContainer.value) {
    viewerContainer.value.style.width = props.containerWidth
    viewerContainer.value.style.height = props.containerHeight
    // console.log(`设置容器尺寸: ${props.containerWidth} x ${props.containerHeight}`)
  }
}

async function ensureValidContainerSize() {
  const container = viewerContainer.value
  if (!container) return
  let retry = 0
  const maxRetries = 10
  let retryDelay = 200
  while (container.clientWidth <= 0 || container.clientHeight <= 0) {
    if (retry >= maxRetries) {
      // console.warn('等待容器尺寸超时，使用默认处理')
      break
    }
    await nextTick()
    await new Promise(resolve => setTimeout(resolve, retryDelay))
    retry++
    retryDelay *= 2
  }
}

async function getHilightProcessInfo(data) {
  await ensureValidContainerSize()
  processXml.value = data
  highlightXml.value = data
  await renderProcess(data)
}

async function renderProcess(data) {
  try {
    isImporting.value = true
    await modeler.value.importXML(data)
    if (props.autoFit) {
      handleFitToScreen()
    }
    isImporting.value = false
  } catch (error) {
    // console.error('流程渲染失败:', error)
    isImporting.value = false
  }
}

function applyHighlight() {
  if (!isChartReady.value || !modeler.value) return
  try {
    const canvasObj = modeler.value.get('canvas')
    const elementRegistry = modeler.value.get('elementRegistry')
    elementRegistry.forEach(element => {
      const isCompleted = props.completedNodes.includes(element.id)
      const isRejected = props.rejectNodes.includes(element.id)
      const currentId = element.id.endsWith('-highlighted')
      if (isCompleted) {
        canvasObj.addMarker(element.id, 'completed-node')
      } else if (isRejected) {
        canvasObj.addMarker(element.id, 'rejected-node')
      } else if (currentId) {
        canvasObj.addMarker(element.id, 'current-node')
      } else {
        canvasObj.removeMarker(element.id, 'completed-node')
      }
    })
  } catch (error) {
    // console.error('应用高亮样式失败:', error)
  }
}

function setZoom(zoomLevel) {
  if (!modeler.value) return
  try {
    const canvasObj = modeler.value.get('canvas')
    canvasObj.zoom(zoomLevel)
    currentZoom.value = zoomLevel
  } catch (error) {}
}

function safeGetZoom() {
  if (!modeler.value) return 1.0
  try {
    const canvasObj = modeler.value.get('canvas')
    const zoom = canvasObj.zoom()
    return isNaN(zoom) ? 1.0 : zoom
  } catch (error) {
    return 1.0
  }
}

function getDiagramBounds(elements) {
  if (!elements || elements.length === 0) {
    return { x: 0, y: 0, width: 100, height: 100 }
  }
  let minX = Infinity
  let minY = Infinity
  let maxX = -Infinity
  let maxY = -Infinity
  elements.forEach(element => {
    if (element.type === 'label') return
    if (element.x < minX) minX = element.x
    if (element.y < minY) minY = element.y
    if (element.x + element.width > maxX) maxX = element.x + element.width
    if (element.y + element.height > maxY) maxY = element.y + element.height
  })
  return {
    x: minX,
    y: minY,
    width: maxX - minX,
    height: maxY - minY
  }
}

function isDiagramCentered() {
  if (!modeler.value) return false
  try {
    const canvasObj = modeler.value.get('canvas')
    const viewbox = canvasObj.viewbox()
    const container = canvas.value
    const containerWidth = container.clientWidth
    const containerHeight = container.clientHeight
    const centerX = containerWidth / 2
    const centerY = containerHeight / 2
    const offsetX = Math.abs(viewbox.x + viewbox.width / 2 - centerX)
    const offsetY = Math.abs(viewbox.y + viewbox.height / 2 - centerY)
    const tolerance = 20
    return offsetX < tolerance && offsetY < tolerance
  } catch (error) {
    return false
  }
}

function scheduleZoomUpdate() {
  retryCount.value = 0
  updateZoomWithRetry()
}

function updateZoomWithRetry() {
  setTimeout(() => {
    const zoom = safeGetZoom()
    if (!isNaN(zoom)) {
      currentZoom.value = zoom
      retryCount.value = 0
    } else if (retryCount.value < maxRetries) {
      const delay = 100 * Math.pow(2, retryCount.value)
      retryCount.value++
      updateZoomWithRetry()
    } else {
      currentZoom.value = 1.0
      retryCount.value = 0
    }
  }, 100 * Math.pow(2, retryCount.value))
}

function onContainerResized() {
  if (isResizing.value) return
  isResizing.value = true
  setTimeout(() => {
    if (isChartReady.value && props.autoFit) {
      handleFitToScreen()
    } else if (isChartReady.value && props.autoCenter) {
      handleCenter()
    }
    isResizing.value = false
  }, 300)
}

function handleWindowResize() {
  onContainerResized()
}

function checkContainerSize() {
  const container = viewerContainer.value
  if (container && (container.clientWidth <= 0 || container.clientHeight <= 0)) {
    updateContainerSize()
    if (isChartReady.value && props.autoFit) {
      handleFitToScreen()
    }
  }
}

function handleZoomIn() {
  zoomInClicks.value++
  if (!canZoom.value) return
  zoomingDisabled.value = true
  setTimeout(() => {
    zoomingDisabled.value = false
  }, 200)
  const newZoom = Math.min(currentZoom.value + zoomStep, zoomMax)
  setZoom(newZoom)
}

function handleZoomOut() {
  zoomOutClicks.value++
  if (!canZoom.value) return
  zoomingDisabled.value = true
  setTimeout(() => {
    zoomingDisabled.value = false
  }, 200)
  const newZoom = Math.max(currentZoom.value - zoomStep, zoomMin)
  setZoom(newZoom)
}

function handleFitToScreen() {
  fitToScreenClicks.value++
  if (!canZoom.value) return
  zoomingDisabled.value = true
  setTimeout(() => {
    zoomingDisabled.value = false
  }, 200)
  fitToScreenWithCenter()
}

function handleCenter() {
  centerClicks.value++
  if (!canZoom.value) return
  zoomingDisabled.value = true
  setTimeout(() => {
    zoomingDisabled.value = false
  }, 200)
  centerDiagram()
}

function fitToScreenWithCenter() {
  if (!modeler.value) return
  try {
    const canvasObj = modeler.value.get('canvas')
    const elementRegistry = modeler.value.get('elementRegistry')
    const elements = elementRegistry.getAll()
    if (elements.length === 0) return
    getDiagramBounds(elements)
    canvasObj.zoom('fit-viewport', {
      minZoom: zoomMin,
      maxZoom: zoomMax,
      padding: props.fitPadding,
      useCSSZoom: false
    })
    viewState.value.isFitted = true
    viewState.value.isCentered = isDiagramCentered()
    scheduleZoomUpdate()
  } catch (error) {}
}

function centerDiagram() {
  if (!modeler.value) return
  try {
    const canvasObj = modeler.value.get('canvas')
    const elementRegistry = modeler.value.get('elementRegistry')
    const elements = elementRegistry.getAll()
    if (elements.length === 0) return
    const bounds = getDiagramBounds(elements)
    const center = {
      x: bounds.x + bounds.width / 2,
      y: bounds.y + bounds.height / 2
    }
    canvasObj.center(center, {
      animate: true,
      duration: 500
    })
    viewState.value.isCentered = true
    scheduleZoomUpdate()
  } catch (error) {}
}

function onDiagramReady() {
  if (isChartReady.value) return
  isChartReady.value = true
  checkContainerSize()
  applyHighlight()
  if (props.autoFit) {
    handleFitToScreen()
  } else if (props.autoCenter) {
    handleCenter()
  }
}

function registerEventListeners() {
  if (!modeler.value) return
  removeEventListeners()
  eventListeners.value = [
    {
      event: 'import.render.complete',
      handler: () => {
        onDiagramReady()
      }
    },
    {
      event: 'diagram.init',
      handler: () => {
        onDiagramReady()
      }
    },
    {
      event: 'canvas.init',
      handler: () => {
        onDiagramReady()
      }
    },
    {
      event: 'bpmnjs.init',
      handler: () => {
        onDiagramReady()
      }
    },
    {
      event: 'import.done',
      handler: (event) => {
        if (!event.error) {
          onDiagramReady()
        }
      }
    },
    {
      event: 'canvas.viewbox.changed',
      handler: (event) => {
        currentZoom.value = event.viewbox.scale
        viewState.value.isCentered = isDiagramCentered()
      }
    }
  ]
  eventListeners.value.forEach(({ event, handler }) => {
    modeler.value.on(event, handler)
  })
}

function removeEventListeners() {
  if (!modeler.value || !eventListeners.value) return
  eventListeners.value.forEach(({ event, handler }) => {
    modeler.value.off(event, handler)
  })
  eventListeners.value = []
}

function initModeler() {
  modeler.value = new BpmnViewer({
    container: canvas.value,
    moddle: {
      modelers: [{ descriptor: camundaModdleDescriptor, priority: 1000 }]
    },
    keyboard: { bindTo: null },
    mouse: {
      drag: { canvas: false, elements: false },
      wheel: false
    },
    propertiesPanel: { parent: null }
  })
  registerEventListeners()
}

onMounted(async () => {
  initModeler()
  await nextTick()
  updateContainerSize()
  if (props.processData) {
    getHilightProcessInfo(props.processData)
  }
  window.addEventListener('resize', handleWindowResize)
})

onBeforeUnmount(() => {
  if (modeler.value) {
    removeEventListeners()
    modeler.value.destroy()
    modeler.value = null
  }
  window.removeEventListener('resize', handleWindowResize)
})

defineExpose({
  getHilightProcessInfo,
});
</script>

<style lang="scss" scoped>
.bpmn-viewer{
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  position: relative;
  background: url("")
  repeat !important;
  div.toggle-mode {
    display: none;
  }
}
.process-top-container{
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
};
.my-process-status {
  display: flex;
  top: 10px;
  left: 20px;
  font-size: 12px;
  .intro {
    color: #999999;
    margin-top: 3px;
  }
  .finish {
    background-color: #E8FFEA;
    padding: 4px;
    border: 1px solid #00B42A;
    border-radius: 3px;
    color: #00B42A;
    margin-right: 8px;
  }
  .processing {
    background-color: #ffd9001e;
    padding: 4px;
    border: 1px solid orange;
    border-radius: 3px;
    color: orange;
    margin-right: 8px;
  }
  .todo {
    padding: 4px;
    background: #fff;
    border: 1px solid black;
    border-radius: 3px;
    color: #666666;
    margin-right: 5px;
  }
}

.zoom-controls {
  display: flex;
  gap: 8px;
  align-items: center;

  .zoom-btn {
    padding: 6px 12px;
    border: 1px solid #ddd;
    border-radius: 4px;
    background-color: #fff;
    cursor: pointer;
    transition: background-color 0.2s;
    outline: none;

    &:hover {
      background-color: #f5f5f5;
    }

    &:active {
      background-color: #eee;
    }

    &:disabled {
      opacity: 0.5;
      cursor: not-allowed;
      background-color: #f5f5f5;
    }
  }

  .zoom-level {
    margin-left: 10px;
    font-size: 12px;
    color: #666;
  }
}
.bpmn-canvas {
  width: 100%;
  height: 100%;
}

::v-deep .completed-node {
      // 设置文字颜色orange
  > .djs-visual text {
    fill: #30b871 !important; /* 设置文字颜色 */
  /* 仅影响图形元素（线条），不影响文本 */
  }
  /* 仅影响图形元素（线条），不影响文本 */
  > .djs-visual > *:not(text) {
    stroke: #30b871 !important; /* 绿色线条 */
    stroke-width: 2px !important; /* 线条宽度 */
    fill: transparent !important; /* 可选：若需保留原有填充色，可移除 */
  }

  /* 可选：单独调整填充色（不影响线条和文字） */
  > .djs-visual > *:not(text):not([fill^="url"]) {
    fill: #E8F5E9 !important; /* 浅绿色填充，不影响渐变填充 */
  }
}
::v-deep .rejected-node {
  /* 仅影响图形元素（线条），不影响文本 */
  > .djs-visual > *:not(text) {
    stroke: red !important; /* 绿色线条 */
    stroke-width: 1px !important; /* 线条宽度 */
    fill: transparent !important; /* 可选：若需保留原有填充色，可移除 */
  }

  /* 可选：单独调整填充色（不影响线条和文字） */
  > .djs-visual > *:not(text):not([fill^="url"]) {
    // fill: #E8F5E9 !important;
  }
}
  ::v-deep .current-node {
    // 设置文字颜色orange
  > .djs-visual text {
    fill: orange !important; /* 设置文字颜色 */
  /* 仅影响图形元素（线条），不影响文本 */
  }
  > .djs-visual > *:not(text) {
    stroke: orange !important; /* 绿色线条 */
    stroke-width: 1px !important; /* 线条宽度 */
    fill: transparent !important; /* 可选：若需保留原有填充色，可移除 */
  }
    /* 可选：单独调整填充色（不影响线条和文字） */
  > .djs-visual > *:not(text):not([fill^="url"]) {
    fill: #ffd9001e !important;
  }
  }

</style>
